Design your console commands for better user experience
To create a new command, we can use the make:command
Artisan command. This command will create a new command class in the app/Console/Commands
directory.
php artisan make:command DataCleaner
Let’s take a look at the example command to clean data on a website.
class DataCleaner extends Command{
protected $signature = 'data:clean { — all} {website}';
protected $description = 'Clean Website Data';
public function __construct() {
parent::__construct();
}
public function handle(
WebsiteRepository $websiteRepository,
CleanerService $cleanerService
){
$website = $this->argument(‘website’);
$all = $this->option(‘all’);
if ( $website ) {
$websiteRepository->filterByFqdn($website);
}
$websites = $websiteRepository->all();
$website->each(function ($site){
$cleanerService->run($site->toArray());
}
);
}
}
Note that we are able to inject any dependencies we need into the command’s handle method. We injected
WebsiteRepository
andCleanerService
class in the handle method above.
The signature
and description
properties of the class will be used when displaying your command on the list screen.
protected $signature = ‘data:clean { — all} {website}’;
The signature
property allows you to define the name, arguments, and options for the command in a single, expressive, route-like syntax.
All user-supplied arguments
and options
are wrapped in curly braces. In the following example, the command defines one required argument: website
.
You may also make arguments optional and define default values for arguments:
data:clean {website?} // Optional argument…
data:clean {website?=sandipshrestha.com.np} // Optional argument with default value…
Options
are prefixed by two hyphens ( — ) when they are specified on the command line. There are two types of options: those that receive a value and those that don’t. Options that don’t receive a value serve as a boolean “switch”. In the following example, the command defines one option: all
.
The handle
method will be called when your command is executed. We place our command logic in this method.
We can access the values for the arguments and options accepted by our command by using the argument and option methods:
// retrieve the website argument value...
$website = $this->argument(‘website’);
// retrieve all of the arguments as an array...
$arguments = $this->arguments();
// Retrieve a specific option...
$all = $this->option('all');
// Retrieve all options...
$options = $this->options();
We can also ask the input value from users by using the following methods.
// prompt the user with a question
$name = $this->ask('What the domain name of the website?');
// similar to ask, but the user's input will be hidden as they type
$password = $this->secret('What is the admin password?');
// asking For Confirmation
$this->confirm('Are you sure? All data will be deleted.')
// used to provide auto-completion for possible choices
$website = $this->anticipate(
'What the domain name of the website?',
['google.com', 'facebook.com']
);
// give the user a predefined set of choices
$website = $this->choice(
'What the domain name of the website?',
['facebook.com', 'google.com', 'linked.in'],
$defaultIndex
);
To send output to our console as feedback, we can use the line
, info
, comment
, question
, warn
, and error
methods. Each of these methods will display the output text in appropriate colors for their purpose. Expect these text output method, we are also provided with two very useful and appealing layouts Table
and Progress Bar
.
The table
method makes it easy to show tabular data. We just need to pass in the headers and rows to the method. The width and height will be dynamically calculated based on the given data:
$this->table(['ID', 'name', 'fqdn'], $websites->toArray());
For iterative or long-running tasks, we could show a progress indicator as feedback to the user. Using the output
object, we can start, advance and stop the Progress Bar. First, define the total number of steps the process will iterate through. Then, advance the Progress Bar using $bar->advance();
after processing each item:
$bar = $this->output->createProgressBar(count($websites));
The final code looks like below:
class DataCleaner extends Command{
protected $signature = ‘data:clean { — all} {website}’;
protected $description = ‘Clean Website Data’;
public function __construct() {
parent::__construct();
}
public function handle(
WebsiteRepository $websiteRepository,
CleanerService $cleanService
){
$website = $this->argument(‘website’);
$all = $this->option(‘all’);
if ( $website ) {
$websiteRepository->filterByFqdn($website);
}
$websites = $websiteRepository->all();
$this->table(
['ID', 'name', 'fqdn'],
$websites->toArray()
);
$bar = $this->output->createProgressBar(count($websites));
$website->each(function ($site){
$cleanService->run($site->toArray());
$this->info("\n".$site->fqdn.' is clean.');
$bar->advance();
});
}
}
These small changes in the interface do actually matter and help make the experience more appealing and intuitive to the user. Now, go and write a beautiful command 😃.
Great things are not done by impulse, but by a series of small things brought together.
— George Elliot